package LDraw.Support; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import Command.LDrawPart; import Command.LDrawQuadrilateral; import Command.LDrawTexture; import Command.LDrawTriangle; import LDraw.Files.LDrawFile; import LDraw.Files.LDrawModel; import LDraw.Files.LDrawStep; import LDraw.Support.type.LDrawDomainT; //============================================================================== // //File: PartLibrary.m // //Purpose: This is the centralized repository for obtaining information // about the contents of the LDraw folder. The part library is // first created by scanning the LDraw folder and collecting all // the part names, categories, and drawing instructions for each // part. This information is then saved into an XML file and // retrieved each time the program is relaunched. During runtime, // other objects query the part library to draw and display // information about parts. // //Created by Allen Smith on 3/12/05. //Copyright 2005. All rights reserved. //============================================================================== public class PartLibrary { // The part catalog was regenerated from disk. // Object is the new catalog. No userInfo. // public static final String LDrawPartLibraryDidChangeNotification = // "LDrawPartLibraryDidChangeNotification"; // The parts list file is stored at LDraw/PARTS_LIST_NAME. // It contains a dictionary of parts. Each element in the dictionary // is an array of parts for a category; the key under which the array // is stored is the category name. // // The part catalog is a dictionary of parts filed by Category name. public static final String PARTS_CATALOG_KEY = "Part Catalog"; // subdictionary keys. public static final String PART_NUMBER_KEY = "Part Number"; public static final String PART_NAME_KEY = "Part Name"; public static final String PART_CATEGORY_KEY = "Category"; public static final String PART_KEYWORDS_KEY = "Keywords"; // Raw dictionary containing each part filed by number. public static final String PARTS_LIST_KEY = "Part List"; // subdictionary keys. // PART_NUMBER_KEY (defined above) // PART_NAME_KEY (defined above) public static final String VERSION_KEY = "Version"; public static final String COMPATIBILITY_VERSION_KEY = "CompatibilityVersion"; public static final String CategoryNameKey = "Name"; public static final String CategoryDisplayNameKey = "DisplayName"; public static final String CategoryChildrenKey = "Children"; public static final String Category_All = "AllCategories"; public static final String Category_Favorites = "Favorites"; public static final String Category_Alias = "Alias"; public static final String Category_Moved = "Moved"; public static final String Category_Primitives = "Primitives"; public static final String Category_Subparts = "Subparts"; private static PartLibrary SharedPartLibrary = null; /** * @uml.property name="delegate" * @uml.associationEnd */ IPartLibraryDelegate delegate; /** * @uml.property name="partCatalog" * @uml.associationEnd * qualifier="constant:java.lang.String java.lang.String" */ HashMap<String, Object> partCatalog; /** * @uml.property name="favorites" * @uml.associationEnd multiplicity="(0 -1)" elementType="java.lang.String" */ ArrayList<String> favorites; // parts names in the "Favorites" // pseduocategory /** * @uml.property name="loadedFiles" * @uml.associationEnd * qualifier="imageName:java.lang.String LDraw.Files.LDrawModel" */ HashMap<String, LDrawDirective> loadedFiles; // list of LDrawFiles which // have been read off disk. /** * @uml.property name="loadedImages" */ HashMap<String, String> loadedImages; /** * @uml.property name="optimizedTextures" * @uml.associationEnd qualifier="name:java.lang.String java.lang.Integer" */ HashMap<String, Integer> optimizedTextures; // GLuint texture tags /** * @uml.property name="optimizedRepresentations" * @uml.associationEnd * qualifier="referenceName:java.lang.String LDraw.Support.LDrawVertices" */ // dispatch_queue_t catalogAccessQueue; // serial queue to mutex changes to // the part catalog /** * @uml.property name="parsingGroups" */ HashMap<String, String> parsingGroups; // arrays of DispatchGroup's which // have requested each file // currently being parsed public PartLibrary() { init(); // reloadParts(); } // ---------- sharedPartLibrary // ---------------------------------------[static]-- // // Purpose: Returns the part libary, which contains the part catalog, which // is read in from the file LDRAW_PATH_KEY/PART_CATALOG_NAME when // the application launches. // This is a rather big XML file, so it behooves us to read it // once then save it in memory. // // ------------------------------------------------------------------------------ public synchronized static PartLibrary sharedPartLibrary() { if (SharedPartLibrary == null) { SharedPartLibrary = new PartLibrary(); } return SharedPartLibrary; }// end sharedPartLibrary // ========== init // ============================================================== // // Purpose: Creates a part library with no parts loaded. // // ============================================================================== private PartLibrary init() { loadedFiles = new HashMap<String, LDrawDirective>(400); loadedImages = new HashMap<String, String>(); optimizedTextures = new HashMap<String, Integer>(); favorites = new ArrayList<String>(); // todo // #if USE_BLOCKS // catalogAccessQueue = // dispatch_queue_create("com.AllenSmith.Bricksmith.CatalogAccess", // null); // #endif parsingGroups = new HashMap<String, String>(); setPartCatalog(new HashMap<String, Object>()); return this; }// end init // // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - // ========== allPartCatalogRecords // ============================================= // // Purpose: Returns all the part numbers in the library. // // ============================================================================== public ArrayList<Object> allPartCatalogRecords() { HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); // all the reference numbers for parts. return (ArrayList<Object>) partList.values(); }// end allPartCatalogRecords // ========== categories // ======================================================== // // Purpose: Returns all the categories in the library, sorted in no // particular order. // // ============================================================================== public ArrayList<String> categories() { HashMap<String, Object> catalogKey = (HashMap<String, Object>) partCatalog .get(PARTS_CATALOG_KEY); return (ArrayList<String>) catalogKey.keySet(); }// end categories // ========== categoryHierarchy // ================================================= // // Purpose: Returns an outline-conducive list of all available categories. // // ============================================================================== public ArrayList<HashMap<String, Object>> categoryHierarchy() { ArrayList<HashMap<String, Object>> fullCategoryList = new ArrayList<HashMap<String, Object>>(); ArrayList<HashMap<String, Object>> libraryItems = new ArrayList<HashMap<String, Object>>(); ArrayList<HashMap<String, Object>> categoryItems = new ArrayList<HashMap<String, Object>>(); ArrayList<HashMap<String, Object>> otherItems = new ArrayList<HashMap<String, Object>>(); // Library group HashMap<String, Object> tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_All); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_All)); libraryItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_Favorites); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_Favorites)); libraryItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, "Library"); tempHashMap.put(CategoryDisplayNameKey, "CategoryGroup_Library"); tempHashMap.put(CategoryChildrenKey, libraryItems); fullCategoryList.add(tempHashMap); // Main categories // todo // sorting �ʿ��ϴ� ArrayList<String> categories = categories(); // ArrayList<String> categories = [categories(). // sortedArrayUsingSelector:@selector(compare:)(); for (String name : categories) { if (name != Category_Alias && name != Category_Moved && name != Category_Primitives && name != Category_Subparts) { tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, name); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(name)); categoryItems.add(tempHashMap); } } tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, "Part Categories"); tempHashMap.put(CategoryDisplayNameKey, "CategoryGroup_PartCategories"); tempHashMap.put(CategoryChildrenKey, categoryItems); fullCategoryList.add(tempHashMap); // Other categories tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_Alias); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_Alias)); otherItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_Moved); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_Moved)); otherItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_Primitives); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_Primitives)); otherItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, Category_Subparts); tempHashMap.put(CategoryDisplayNameKey, displayNameForCategory(Category_Subparts)); otherItems.add(tempHashMap); tempHashMap = new HashMap<String, Object>(); tempHashMap.put(CategoryNameKey, "Other"); tempHashMap.put(CategoryDisplayNameKey, "CategoryGroup_Other"); tempHashMap.put(CategoryChildrenKey, otherItems); fullCategoryList.add(tempHashMap); return fullCategoryList; }// end categoryHierarchy // ========== categoryForPartName: // ============================================== // // Purpose: Returns the part's category. // // ============================================================================== public String categoryForPartName(String partName) { HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); HashMap<String, Object> catalogInfo = (HashMap<String, Object>) partList .get(partName); String category = (String) (catalogInfo.get(PART_CATEGORY_KEY)); return category; } // ========== favoritePartNames // ================================================= // // Purpose: Returns all the part names the user has bookmarked as his // favorites. // // ============================================================================== public ArrayList<String> favoritePartNames() { return favorites; }// end favoritePartNames // ========== displayNameForCategory: // =========================================== // // Purpose: Returns the human-friendly category name // // ============================================================================== public String displayNameForCategory(String categoryName) { String displayName = null; if (categoryName == Category_All) { displayName = "AllCategories"; } else if (categoryName == Category_Favorites) { displayName = "FavoritesCategory"; } else { displayName = categoryName; } return displayName; } // ========== favoritePartCatalogRecords // ======================================== // // Purpose: Returns all the part info records the user has bookmarked as his // favorites. // // ============================================================================== public ArrayList<Object> favoritePartCatalogRecords() { HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); ArrayList<Object> parts = new ArrayList<Object>(); HashMap<String, Object> partInfo = null; for (String partName : favorites) { partInfo = (HashMap<String, Object>) partList.get(partName); if (partInfo != null) parts.add(partInfo); } return parts; }// end favoritePartNames // ========== partCatalogRecordsInCategory: // ===================================== // // Purpose: Returns all the parts in the given category. Returns null if the // category doesn't exist. // // ============================================================================== public ArrayList<Object> partCatalogRecordsInCategory(String categoryName) { ArrayList<Object> parts = null; if (categoryName == Category_All) { // Retrieve all parts. We can do this by getting the entire // (unsorted) // contents of PARTS_LIST_KEY in the partCatalog, which is actually // a dictionary of all parts. parts = allPartCatalogRecords(); } else if (categoryName == Category_Favorites) { parts = favoritePartCatalogRecords(); } else { HashMap<String, Object> partCatalogMap = (HashMap<String, Object>) partCatalog .get(PARTS_CATALOG_KEY); ArrayList<HashMap<String, Object>> category = (ArrayList<HashMap<String, Object>>) partCatalogMap .get(categoryName); HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); ArrayList<Object> partsInCategory = new ArrayList<Object>(); String partName = null; HashMap<String, Object> partInfo = null; for (HashMap<String, Object> categoryRecord : category) { partName = (String) categoryRecord.get(PART_NUMBER_KEY); partInfo = (HashMap<String, Object>) partList.get(partName); if (partInfo != null) partsInCategory.add(partInfo); } parts = partsInCategory; } return parts; }// end partCatalogRecordsInCategory: // #pragma mark - // ========== setDelegate: // ====================================================== // // Purpose: Set the object responsible for receiving important notifications // from us. // // ============================================================================== /** * @param delegateIn * @uml.property name="delegate" */ public void setDelegate(IPartLibraryDelegate delegateIn) { delegate = delegateIn; } // ========== setFavorites: // ===================================================== // // Purpose: Sets the parts which should appear in the Favorites category. // This list should have been saved in preferences and loaded by // the part library controller. // // ============================================================================== public void setFavorites(ArrayList<String> favoritesIn) { favorites.clear(); favorites.addAll(favoritesIn); } // ========== setPartCatalog // ==================================================== // // Purpose: Saves the local instance of the part catalog, which should be // the only copy of it in the program. Use +setSharedPartCatalog to // update it outside this class. // // Notes: The Part Catalog is structured as follows: // // partCatalog // | // |--> PARTS_CATALOG_KEY <HashMap<String,String>> // | | // | Keys are category names, e.g., "Brick" // | <ArrayList<String>> // | | // | <HashMap<String,String>> // | |--> PART_NUMBER_KEY <String> (e.g., "3001.dat") // | // |--> PARTS_LIST_KEY <HashMap<String,String>> // | // Keys are part reference numbers, e.g., "3001.dat" // <HashMap<String,String>> // |--> PART_NUMBER_KEY // |--> PART_NAME_KEY // // This data structure is PRIVATE. There is no get accessor. Query // this object for its part lists and build your own records. // // ============================================================================== public void setPartCatalog(HashMap<String, Object> newCatalog) { partCatalog = newCatalog; // Inform any open parts browsers of the change. // //todo // [[NSNotificationCenter defaultCenter] // postNotificationName: LDrawPartLibraryDidChangeNotification // object: self (); }// end setPartCatalog // #pragma mark - // #pragma mark ACTIONS // #pragma mark - // ========== load // ============================================================== // // Purpose: Loads the catalog from the part list stashed in the LDraw // folder. // // Returns: false if no part list exists. (You need to call -reloadParts: in // PartLibraryController then.) // // ============================================================================== public boolean load() { // NSFileManager *fileManager = [[[NSFileManager alloc] init] // autorelease(); String catalogPath = LDrawPaths.sharedPaths().partCatalogPath(); boolean partsListExists = false; String version = null; HashMap<String, Object> newCatalog = null; // Do we have an LDraw folder? if (catalogPath != null) { if (new File(catalogPath).isFile()) partsListExists = true; } // Do we have a part list already? if (partsListExists == true) { // todo // newCatalog = [HashMap<String,String> // dictionaryWithContentsOfFile:catalogPath(); version = (String) newCatalog.get(VERSION_KEY); if (version != null) { setPartCatalog(newCatalog); } else { // Older part catalogs don't have enough info in them partsListExists = false; } } return partsListExists; }// end load // ========== reloadParts: // ====================================================== // // Purpose: Scans the contents of the LDraw/ folder and produces a // Mac-friendly index of parts. // // Is it fast? No. Is it easy to code? Yes. // // Someday in the rosy future, this method should be recoded to // simply traverse the directory tree and deal with subfolders on // the fly. But that's not how it is now. Instead, I'm doing it // all manually. Folders searched are: // // LDraw/p/ // LDraw/p/48/ // // LDraw/parts/ // LDraw/parts/s/ // // LDraw/Unofficial/p/ // LDraw/Unofficial/p/48/ // LDraw/Unofficial/parts/ // LDraw/Unofficial/parts/s/ // // It is important that the part name added to the library bear // the correct reference style. For LDraw/p/ and LDraw/parts/, it // is simply the filename (in lowercase). But for subdirectories, // the filename must be prefixed with the subdirectory in DOS // format, i.e., "s\file.dat" or "48\file.dat". // // ============================================================================== public boolean reloadParts() { // NSFileManager *fileManager = [[[NSFileManager alloc] init] // autorelease(); LDrawPaths sharedPaths = LDrawPaths.sharedPaths(); String ldrawPath = sharedPaths.preferredLDrawPath(); ArrayList<Object> searchPaths = new ArrayList<Object>(); String prefix_primitives48 = String.format("%s\\", LDrawPaths.PRIMITIVES_48_DIRECTORY_NAME); String prefix_subparts = String.format("%s\\", LDrawPaths.SUBPARTS_DIRECTORY_NAME); // make sure the LDraw folder is still valid; otherwise, why bother // doing anything? if (sharedPaths.validateLDrawFolder(ldrawPath) == false) return false; // Parts HashMap<String, String> tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths.partsPathForDomain(LDrawDomainT.LDrawUserOfficial)); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawUserUnofficial)); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalOfficial)); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalUnofficial)); searchPaths.add(tempHashMap); // Primitives tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalUnofficial)); tempHashMap.put("category", Category_Primitives); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalOfficial)); tempHashMap.put("category", Category_Primitives); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalOfficial)); tempHashMap.put("category", Category_Primitives); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalUnofficial)); tempHashMap.put("category", Category_Primitives); searchPaths.add(tempHashMap); // Primitives 48 tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths.partsPathForDomain(LDrawDomainT.LDrawUserOfficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_primitives48); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawUserUnofficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_primitives48); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalOfficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_primitives48); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalUnofficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_primitives48); searchPaths.add(tempHashMap); // Subparts tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths.partsPathForDomain(LDrawDomainT.LDrawUserOfficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_subparts); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawUserUnofficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_subparts); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalOfficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_subparts); searchPaths.add(tempHashMap); tempHashMap = new HashMap<String, String>(); tempHashMap.put("path", sharedPaths .partsPathForDomain(LDrawDomainT.LDrawInternalUnofficial)); tempHashMap.put("category", Category_Primitives); tempHashMap.put("prefix", prefix_subparts); searchPaths.add(tempHashMap); String partCatalogPath = sharedPaths.partCatalogPath(); HashMap<String, Object> newPartCatalog = new HashMap<String, Object>(); int partCount = 0; // Start the progress bar so that we know what's happening. for (Object item : searchPaths) { String path = ((HashMap<String, String>) item).get("path"); File dir = new File(path); if (!dir.exists() || !dir.isDirectory()) { System.out.println("Invalid path: " + path); continue; } File[] files = dir.listFiles(); partCount += files.length; } // delegate.partLibrary(this, partCount); // Create the new part catalog. We will then fill it with folder // contents. newPartCatalog.put(PARTS_CATALOG_KEY, new HashMap<String, Object>()); newPartCatalog.put(PARTS_LIST_KEY, new HashMap<String, Object>()); // Scan for each part folder. for (Object item : searchPaths) { HashMap<String, String> record = (HashMap<String, String>) item; addPartsInFolder(record.get("path"), newPartCatalog, record.get("category"), // override all internal categories record.get("prefix")); } // todo // String version = [[[NSBundle mainBundle] // infoDictionary].get(@"CFBundleVersion"(); // [newPartCatalog setObject:version VERSION_KEY(); // [newPartCatalog setObject:@"1.0" COMPATIBILITY_VERSION_KEY(); // Save the part catalog out for future reference. // todo // newPartCatalog.writeToFile:partCatalogPath atomically:true(); // setPartCatalog(newPartCatalog); // [[NSNotificationCenter defaultCenter] // postNotificationName:LDrawPartLibraryReloaded object:self (); // We succeeded in loading the parts! return true; }// end reloadParts: // #pragma mark - // #pragma mark FAVORITES // #pragma mark - // ========== addPartNameToFavorites: // =========================================== // // Purpose: Adds the given part name to the "Favorites" category. // // ============================================================================== public void addPartNameToFavorites(String partName) { favorites.add(partName); saveFavoritesToUserDefaults(); // Inform any open parts browsers of the change. // todo // [[NSNotificationCenter defaultCenter] // postNotificationName: LDrawPartLibraryDidChangeNotification // object: self (); }// end addPartNameToFavorites: // ========== removePartNameFromFavorites: // ====================================== // // Purpose: Removes the given part name to the "Favorites" category. // // ============================================================================== public void removePartNameFromFavorites(String partName) { favorites.remove(partName); saveFavoritesToUserDefaults(); // Inform any open parts browsers of the change. // todo // [[NSNotificationCenter defaultCenter] // postNotificationName: LDrawPartLibraryDidChangeNotification // object: self (); }// end removePartNameFromFavorites: // ========== saveFavoritesToUserDefaults // ======================================= // // Purpose: Writes the favorite parts list to preferences. // // ============================================================================== public void saveFavoritesToUserDefaults() { delegate.partLibrary(this, favorites); }// end saveFavoritesToUserDefaults // #pragma mark - // #pragma mark FINDING PARTS // #pragma mark - // ========== loadImageForName:inGroup: // ========================================= // // Purpose: This is a thread-safe method which causes the texture image of // the given name to be loaded out of the LDraw folder. // // ============================================================================== public void loadImageForName(String imageName, DispatchGroup parentGroup) { // todo // Determine if the model needs to be parsed. // Dispatch to a serial queue to effectively mutex the query // #if USE_BLOCKS // dispatch_group_async(parentGroup, catalogAccessQueue, // ^{ // ArrayList<String> requestingGroups = null; // #endif // CGImageRef image = null; // boolean alreadyParsing = false; // another thread is already parsing // partName // // // Already been parsed? // image = (CGImageRef)loadedImages.get(imageName(); // if(image == null) // { // #if USE_BLOCKS // // Is it being parsed? If so, all we need to do is wait for whoever // // is parsing it to finish. // requestingGroups = parsingGroups.get(imageName(); // alreadyParsing = (requestingGroups != null); // // if(alreadyParsing == false) // { // // Start a registry for all the dispatch groups which attempt to // // load the same model. When parsing is complete, they will all // // be signaled. // requestingGroups = [[NSMutableArray alloc] init(); // parsingGroups setObject:requestingGroups imageName(); // [requestingGroups release(); // } // // // Register the calling group as having also requested a parse // // for this file. This ensures the calling group cannot complete // // until the parse is complete on whatever thread is actually // // doing it. // dispatch_group_enter(parentGroup); // [requestingGroups addObject(NSValue valueWithPointer:parentGroup](); // #endif // Nobody has started parsing it yet, so we win! Parse from disk. // if(alreadyParsing == false) // { // #if USE_BLOCKS // dispatch_group_async(parentGroup, // dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), // ^{ // #endif // String *imagePath = // LDrawPaths.sharedPaths().pathForTextureName:imageName(); // #if USE_BLOCKS // //------------------------------------------------------ // readImageAtPath:imagePath asynchronously:true // completionHandler:^(CGImageRef image) // { // if(image) CFRetain(image); // // // Register new image in the library (serial queue "mutex" protected) // dispatch_group_async(parentGroup, catalogAccessQueue, // ^{ // if(image != null) // { // loadedImages setObject:(id)image imageName(); // } // // // Notify waiting threads we are finished parsing this part. // for(NSValue *waitingGroupPtr in requestingGroups) // { // DispatchGroup waitingGroup = [waitingGroupPtr pointerValue(); // dispatch_group_leave(waitingGroup); // } // parsingGroups removeObjectForKey:imageName(); // // if(image) CFRelease(image); // }); // }(); // #else // //------------------------------------------------------------------------ // **** Non-multithreaded fallback code **** // image = (CGImageRef)readImageAtPath:imagePath asynchronously:false // completionHandler:null(); // if(image != null) // { // loadedImages setObject:(id)image imageName(); // } // #endif // //----------------------------------------------------------------------- // #if USE_BLOCKS // }); // #endif // } // } // #if USE_BLOCKS // }); // #endif }// end loadImageForName: // ========== loadModelForName:inGroup: // ========================================= // // Purpose: This is a thread-safe method which causes the model of the given // name to be loaded out of the LDraw folder. // // ============================================================================== public void loadModelForName(String partName, String referenceName, DispatchGroup parentGroup) { LDrawModel model = null; // Already been parsed? model = (LDrawModel) loadedFiles.get(referenceName); if (model == null) { // Nobody has started parsing it yet, so we win! Parse from disk. String partPath = LDrawPaths.sharedPaths() .pathForPartName(partName); model = readModelAtPath(partPath, parentGroup, null); if (model != null) { loadedFiles.put(referenceName, model); } } }// end loadModelForName: // ========== imageForTextureName: // ============================================== // // Purpose: Returns an image from our library cache. // // ============================================================================== // - (CGImageRef) imageForTextureName(String imageName // { // CGImageRef image = null; // String imagePath = null; // // // Has it already been parsed? // image = (CGImageRef)loadedImages.get(imageName(); // // if(image == null) // { // // Well, this means we have to try getting it off the disk! // imagePath = LDrawPaths.sharedPaths().pathForTextureName:imageName(); // image = readImageAtPath:imagePath asynchronously:false // completionHandler:null(); // // if(image != null) // loadedImages setObject:(id)image imageName(); // } // // return image; // // } // ========== imageForTexture: // ================================================== // // Purpose: Returns the image specified by the texture object. // // ============================================================================== // - (CGImageRef) imageForTexture:(LDrawTexture *)texture // { // String imageName = [texture imageReferenceName(); // CGImageRef image = null; // // // Try to get a live link if we have parsed this part off disk already. // image = imageForTextureName:imageName(); // // if(image == null) { // //we're grasping at straws. See if this is a reference to an external // // file in the same folder. // image = imageFromNeighboringFileForTexture:texture(); // } // // return image; // // }//end imageForTexture: // ========== imageFromNeighboringFileForTexture: // =============================== // // Purpose: Attempts to resolve the texture's name reference against a file // located in the same parent folder as the file in which the part // is contained. // // This should be a method of last resort, after searching the part // library. // // Note: This is BAD CODE. It caches things permanently. We need to move // to the new model manager to track when to get rid of images. // // ============================================================================== // - (CGImageRef) imageFromNeighboringFileForTexture:(LDrawTexture *)texture // { // LDrawFile *enclosingFile = [texture enclosingFile(); // String filePath = [enclosingFile path(); // String fileDirectory = null; // String imageName = null; // String testPath = null; // String imagePath = null; // CGImageRef image = null; // NSFileManager *fileManager = null; // // if(filePath != null) // { // fileManager = [[[NSFileManager alloc] init] autorelease(); // fileDirectory = [filePath stringByDeletingLastPathComponent(); // imageName = [texture imageDisplayName(); // handle case-sensitive // filesystem // // // look at path = parentFolder/textures/name // { // testPath = [fileDirectory // stringByAppendingPathComponent:TEXTURES_DIRECTORY_NAME(); // testPath = [testPath stringByAppendingPathComponent:imageName(); // if([fileManager fileExistsAtPath:testPath]) // { // imagePath = testPath; // } // } // // //look at path = parentFolder/name // if(imagePath == null) // { // testPath = [fileDirectory stringByAppendingPathComponent:imageName(); // if([fileManager fileExistsAtPath:testPath]) // { // imagePath = testPath; // } // } // // // Load if we found something // if(imagePath) // { // image = readImageAtPath:testPath asynchronously:false // completionHandler:null(); // if(image != null) // loadedImages setObject:(id)image imageName(); // } // } // // return image; // // }//end imageFromNeighboringFileForTexture: // ========== modelForName: // ===================================================== // // Purpose: Attempts to find the part based only on the given name. // This method can only find parts in the LDraw folder; it returns // null if fed an MPD submodel name. // // falseT THREAD SAFE! // // Notes: The part is looked up by the name specified in the part command. // For regular parts and primitives, this is simply the filename // as found in LDraw/parts or LDraw/p. But for subparts found in // LDraw/parts/s, the filename is "s\partname.dat". (Same goes for // LDraw/p/48.) This icky inconsistency is handled in // -pathForFileName:. // // ============================================================================== public LDrawModel modelForName(String imageName) { LDrawModel model = null; String partPath = null; // Has it already been parsed? model = (LDrawModel) loadedFiles.get(imageName); if (model == null) { // System.out.println("ImageName:"+imageName); // Well, this means we have to try getting it off the disk! // This case is only hit when a library part uses another library // part, e.g. // a brick grabs a collection-of-studs part. partPath = LDrawPaths.sharedPaths().pathForPartName(imageName); model = readModelAtPath(partPath, new DispatchGroup(), null); if (model != null) { loadedFiles.put(imageName, model); } } return model; }// end modelForName // ========== modelForPartInternal: // ===================================================== // // Purpose: Returns the model to which this part refers. You can then ask // the model to draw itself. // // falseT THREAD SAFE! // // Notes: The part is looked up by the name specified in the part command. // For regular parts and primitives, this is simply the filename // as found in LDraw/parts or LDraw/p. But for subparts found in // LDraw/parts/s, the filename is "s\partname.dat". (Same goes for // LDraw/p/48.) This icky inconsistency is handled in // -pathForFileName:. // // This has been marked "internal" because the API is now only used // _within_ the part library, not by public clients. // // ============================================================================== public LDrawModel modelForPartInternal(LDrawPart part) { String imageName = part.referenceName(); LDrawModel model = null; // Try to get a live link if we have parsed this part off disk already. // Ben sez: This routine is currently authorized to load on demand, but // I never see that code run and I don't think it is suppose to. model = modelForName(imageName); if (model == null) { // We didn't find it in the LDraw folder. Hopefully this is a // reference // to another model in an MPD file. model = part.referencedMPDSubmodel(); } return model; }// end modelForPartInternal: // ========== modelForName_threadSafe: // ========================================== // // Purpose: Returns the model to which this part name refers, thread-safe. // // Notes: This will falseT attempt to read the file off disk. This method is // only intended to be called during the multi-threaded file // loading process, so there should be no need to do lazy loading. // // ============================================================================== public LDrawModel modelForName_threadSafe(String imageName) { LDrawModel model = null; // #if USE_BLOCKS // dispatch_sync(catalogAccessQueue, ^{ // #endif model = (LDrawModel) loadedFiles.get(imageName); // #if USE_BLOCKS // }); // #endif return model; } // ========== textureTagForTexture: // ============================================= // // Purpose: Returns the OpenGL tag necessary to draw the image represented // by the high-level texture object. // // ============================================================================== public int textureTagForTexture(LDrawTexture texture) { String name = texture.imageReferenceName(); Integer tagNumber = optimizedTextures.get(name); int textureTag = 0; if (tagNumber != null) { textureTag = tagNumber.intValue(); } else { // todo // CGImageRef image = imageForTexture:texture(); // // if(image) // { // CGRect canvasRect = CGRectMake( 0, 0, // FloorPowerOfTwo(CGImageGetWidth(image)), // FloorPowerOfTwo(CGImageGetHeight(image)) ); // uint8_t *imageBuffer = malloc( (canvasRect.size.width) * // (canvasRect.size.height) * 4 ); // CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // CGContextRef bitmapContext = CGBitmapContextCreate(imageBuffer, // canvasRect.size.width, // canvasRect.size.height, // 8, // bits per component // canvasRect.size.width * 4, // bytes per row // colorSpace, // kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst // ); // // // Draw the image into the bitmap context. By doing so, we use // the mighty // // power of Quartz handle the nasty conversion details necessary // to fill up // // a pixel buffer in an OpenGL-friendly storage format and color // space. // CGContextSetBlendMode(bitmapContext, kCGBlendModeCopy); // CGContextDrawImage(bitmapContext, canvasRect, image); // // // CGImageRef output = CGBitmapContextCreateImage(bitmapContext); // // CGImageDestinationRef myImageDest = // CGImageDestinationCreateWithURL((CFURLRef)[NSURL // fileURLWithPath:@"/out.png"], kUTTypePNG, 1, null); // // //HashMap<String,String>* options = [HashMap<String,String> // dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:1.0], // kCGImageDestinationLossyCompressionQuality, null(); // Don't know // if this is necessary // // CGImageDestinationAddImage(myImageDest, output, null); // // CGImageDestinationFinalize(myImageDest); // // CFRelease(myImageDest); // // // Generate a tag for the texture we're about to generate, then // set it as // // the active texture. // // Note: We are using non-rectangular textures here, which // started as an // // extension (_EXT) and is now ratified by the review board // (_ARB) // glGenTextures(1, &textureTag); // glBindTexture(GL_TEXTURE_2D, textureTag); // // // Generate Texture! // glPixelStorei(GL_PACK_ROW_LENGTH, canvasRect.size.width * 4); // glPixelStorei(GL_PACK_ALIGNMENT, 1); // byte alignment // // glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, // texture type params // canvasRect.size.width, canvasRect.size.height, 0, // source image // (w, h) // GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, // source storage format // imageBuffer ); // // see function notes about the source storage format. // // // This requires GL_EXT_framebuffer_object, available on all // renderers on 10.6.8 and beyond. // // Build mipmaps so we can use linear-mipmap-linear // glGenerateMipmapEXT(GL_TEXTURE_2D); // // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, // GL_LINEAR_MIPMAP_LINEAR); // This enables mip-mapping - makes // textures look good when small. // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, // 4.0); // Max anisotropic filtering of all renderers on 10.6.8 is // 16.0. // // This keeps texture res high when looking at a tile from a low // angle. // // glBindTexture(GL_TEXTURE_2D, 0); // // optimizedTextures setObject(NSNumber // numberWithUnsignedInt:textureTag] name(); // // // free memory // // free(imageBuffer); // CFRelease(colorSpace); // CFRelease(bitmapContext); // } } return textureTag; } // #pragma mark - // #pragma mark UTILITIES // #pragma mark - // ========== addPartsInFolder:toCatalog:underCategory: // ========================= // // Purpose: Scans all the parts in folderPath and adds them to the given // catalog, filing them under the given category. Pass null for // category if you wish to use the categories defined in the parts // themselves. // // Parameters: categoryOverride - force all parts in the folder to be filed // under this category, rather than the one // defined inside the part. // namePrefix - appends this prefix to each part scanned. // Part references in LDraw/parts/s should be // prefixed with the DOS path "s\". Pass null // to ignore the prefix. // progressPanel - a progress panel which is displaying the // progress of the creation of the part // catalog. // // ============================================================================== public void addPartsInFolder(String folderPath, HashMap<String, Object> catalog, String categoryOverride, String namePrefix) { // NSFileManager *fileManager = [[[NSFileManager alloc] init] // autorelease(); // Not working for some reason. Why? // ArrayList<String> *readableFileTypes = [NSDocument readableTypes(); // System.out.println(String.format("readable types: %s", // readableFileTypes); ArrayList<String> readableFileTypes = new ArrayList<String>(); readableFileTypes.add("dat"); readableFileTypes.add("ldr"); ArrayList<String> partNames = new ArrayList<String>(); if (new File(folderPath).isDirectory()) for (File file : new File(folderPath).listFiles()) if (file.isFile()) partNames.add(file.getName()); int numberOfParts = partNames.size(); int counter; String currentPath = null; HashMap<String, String> categoryRecord = null; // Get the subreference tables out of the main catalog (they should // already exist!). HashMap<String, Object> catalog_partNumbers = (HashMap<String, Object>) catalog .get(PARTS_LIST_KEY); // lookup parts by number HashMap<String, Object> catalog_categories = (HashMap<String, Object>) catalog .get(PARTS_CATALOG_KEY); // lookup parts by category ArrayList<Object> catalog_category = null; // Loop through the entire contents of the directory and extract the // information for every part therein. for (counter = 0; counter < numberOfParts; counter++) { String partName = partNames.get(counter); currentPath = folderPath + partName; String[] temp = partName.split("\\."); String ext = null; if (temp.length > 1) ext = temp[1]; if (readableFileTypes.contains(ext) == true) { categoryRecord = catalogInfoForFileAtPath(currentPath); // Make sure the part file was valid! if (categoryRecord != null && categoryRecord.size() > 0) { // ---------- Alter catalog info // -------------------------------- if (categoryOverride != null) categoryRecord.put(PART_CATEGORY_KEY, categoryOverride); // Parts in subfolders of LDraw/parts must have a name // prefix of // their subpath, e.g., "s\partname.dat" for a part in the // LDraw/parts/s folder. if (namePrefix != null) { String partNumber = null; partNumber = categoryRecord.get(PART_NUMBER_KEY); partNumber = namePrefix + partNumber; categoryRecord.put(PART_NUMBER_KEY, partNumber); } // ---------- Catalog the part // ---------------------------------- String category = categoryRecord.get(PART_CATEGORY_KEY); if (category != null) { catalog_category = (ArrayList<Object>) catalog_categories .get(category); if (catalog_category == null) { // We haven't encountered this category yet. // Initialize it now. catalog_category = new ArrayList<Object>(); catalog_categories.put(category, catalog_category); } // For some reason, I made each entry in the category a // dictionary with part info. This was a database design // mistake; it should have been an array of part // reference // numbers, if not just built up at runtime. String categoryEntry = categoryRecord .get(PART_NUMBER_KEY); catalog_category.add(categoryEntry); // Also file the part in a master list by reference // name. catalog_partNumbers.put( categoryRecord.get(PART_NUMBER_KEY), categoryRecord); } // System.out.println(String.format("processed %s", // [partNames objectAtIndex:counter]); } } // todo // delegate.partLibraryIncrementLoadProgressCount(this); }// end loop through files }// end addPartsInFolder:toCatalog:underCategory: // ========== categoryForDescription: // =========================================== // // Purpose: Returns the category for the given modelDescription. This is // the first line of the file for non-MPD documents. For instance: // // 0 Brick 2 x 4 // // This part would be in the category "Brick", and has the // description "Brick 2 x 4". // // ============================================================================== public String categoryForDescription(String modelDescription) { String category = null; int firstSpace; // range of the category string in the first line. // The category name is the first word in the description. firstSpace = modelDescription.indexOf(" "); if (firstSpace != -1) category = modelDescription.substring(firstSpace); else category = modelDescription; // Deal with any weird notational marks // Alias parts begin with an underscore. These things are so annoying // I'm // going to dump them in a pseudo category. This is kind of a hack, but // at // least it's a prettifying one. if (category.charAt(0) == '_') { category = Category_Alias; } // Moved parts always begin with ~Moved, which is ugly. We'll strip the // '~'. else if (category.charAt(0) == '~') { category = category.substring(1); } return category; }// end categoryForDescription: // ========== descriptionForPart: // =============================================== // // Purpose: Returns the description of the given part based on its name. // // ============================================================================== public String descriptionForPart(LDrawPart part) { // Look up the verbose part description in the scanned part catalog. HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); HashMap<String, Object> partRecord = null; if (partList != null) partRecord = (HashMap<String, Object>) partList.get(part .getReferenceName()); String partDescription = null; if (partRecord != null) partDescription = (String) partRecord.get(PART_NAME_KEY); // Maybe it's an MPD reference? if (partDescription == null) { LDrawModel mpdModel = part.referencedMPDSubmodel(); if (mpdModel != null) partDescription = mpdModel.browsingDescription(); } // If the part STILL isn't known, all we can really do is just display // the // number. if (partDescription == null) { partDescription = part.displayName(); } return partDescription; }// end descriptionForPart: // ========== descriptionForPartName: // =========================================== // // Purpose: Returns the description associated with the given part name. // For example, passing "3001.dat" returns "Brick 2 x 4". // If the name isn't known to the Part Library, we just return name. // // Note: If you have a reference to the LDrawPart itself, you should pass // it to -descriptionForPart instead. // // ============================================================================== public String descriptionForPartName(String name) { // Look up the verbose part description in the scanned part catalog. HashMap<String, Object> partList = (HashMap<String, Object>) partCatalog .get(PARTS_LIST_KEY); HashMap<String, Object> partRecord = (HashMap<String, Object>) partList .get(name); String partDescription = (String) partRecord.get(PART_NAME_KEY); // If the part isn't known, all we can really do is just display the // number. if (partDescription == null) partDescription = name; return partDescription; }// end descriptionForPartName: // ========== catalogInfoForFileAtPath: // ========================================= // // Purpose: Pulls out the catalog-relevate metadata out of the given file. // By convention, the first line of an non-MPD LDraw file is the // description; e.g., // // 0 Brick 2 x 4 // // This part is thus in the category "Brick", and has the // description "Brick 2 x 4". // // Returns: null if the file is not valid. // // PART_NUMBER_KEY string // PART_CATEGORY_KEY string // PART_KEYWORDS_KEY array // PART_NAME_KEY string // // ============================================================================== public HashMap<String, String> catalogInfoForFileAtPath(String filepath) { // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init(); String fileContents = LDrawUtilities.stringFromFile(filepath); // NSCharacterSet *whitespace = [NSCharacterSet // whitespaceAndNewlineCharacterSet(); String partNumber = null; String partDescription = null; String category = null; ArrayList<String> keywords = null; HashMap<String, String> catalogInfo = null; // Read the first line of the file. Make sure the file is parsable. if (fileContents != null && fileContents.length() > 0) { int stringLength = fileContents.length(); int lineStartIndex = 0; int nextlineStartIndex = 0; int newlineIndex = 0; // index of the first newline character in the // line. int lineLength = 0; // String line = null; String lineCode = null; // ByteBuffer lineRemainder = null; catalogInfo = new HashMap<String, String>(); // Get the name of the part. // We need a standard way to reference it; use lower-case to avoid // any // case-sensitivity issues. partNumber = new File(filepath).getName().split("\\.")[0] .toLowerCase(); catalogInfo.put(PART_NUMBER_KEY, partNumber); String[] lines = fileContents.replace("\r", "").split("\n"); for (String line : lines) { // LDraw uses DOS lineendings // fileContents.getLineStart(lineStartIndex, nextlineStartIndex, // newlineIndex, new Range(nextlineStartIndex,1) ); //that is, // contains the first character. // lineLength = newlineIndex - lineStartIndex; // line = fileContents.substring(lineStartIndex, newlineIndex); // line = lines[] StringTokenizer strTokenizer = new StringTokenizer(line); lineCode = strTokenizer.nextToken(); // Check to see if this is a valid LDraw header. if (lineStartIndex == 0) { if (lineCode != "0") break; String strTemp = ""; while (strTokenizer.hasMoreTokens()) strTemp += strTokenizer.nextToken() + " "; partDescription = new String(strTemp); catalogInfo.put(PART_NAME_KEY, partDescription); break; } // todo ������ �� �ʿ�����? // else if(lineCode =="0") // { // // Try to find keywords or category // String *meta =[LDrawUtilities.readNextField:lineRemainder // remainder:&lineRemainder(); // // if([meta ==LDRAW_CATEGORY]) // { // category = [lineRemainder // stringByTrimmingCharactersInSet:whitespace(); // // // Turns out !CATEGORY is not as reliable as it ought to be. // // In typical LDraw fashion, the feature was not have a // // simultaneous, universal deployment. Unfortunately, the // // only categories I deem to be consistent and advantageous // // under the current system are the two-word categories that // // couldn't be represented under the old system. // // // // Also, allow the !LDRAW_ORG Part Alias to take precedence // // if it has already been found. // if( [category rangeOfString:@" "].location != NSNotFound // && catalogInfo.get(PART_CATEGORY_KEY] == null ) // { // catalogInfo.put(category PART_CATEGORY_KEY(); // } // } // else if([meta ==LDRAW_KEYWORDS]) // { // if(keywords == null) // { // keywords =new ArrayList<String>(); // catalogInfo.put(keywords PART_KEYWORDS_KEY(); // } // // Keywords can be multiline, so must add to any we've // already collected! // ArrayList<> newKeywords = [lineRemainder // componentsSeparatedByCharactersInSet(NSCharacterSet // characterSetWithCharactersInString:@","](); // for(String *keyword in newKeywords) // { // [keywords addObject(keyword // stringByTrimmingCharactersInSet:whitespace](); // } // } // else if(meta ==LDRAW_ORG) // { // // Force alias parts into a ghetto category which will keep // // them far away from normal building. // String officialStatus = lineRemainder.trim(); // if[officialStatus.contains("Part Alias")) // { // category = Category_Alias; // catalogInfo.put(PART_CATEGORY_KEY, category); // } // } // } // else if([lineCode length] == 0) // { // // line is blank. Skip. // } // else // { // // Non-comment, non-blank line. This cannot be part of the // header. // break; // } } // If no !CATEGORY directive, the the category is to be derived from // the // first word of the description. if (catalogInfo.get(PART_NAME_KEY) != null && catalogInfo.get(PART_CATEGORY_KEY) == null) { partDescription = catalogInfo.get(PART_NAME_KEY); category = categoryForDescription(partDescription); catalogInfo.put(PART_CATEGORY_KEY, category); } } else { System.out.println(String .format("%s is not a valid file", filepath)); } // [catalogInfo retain(); // [pool drain(); return catalogInfo; }// end catalogInfoForFileAtPath // ========== readImageAtPath: // ================================================== // // Purpose: Parses the model found at the given path, adds it to the list of // loaded parts, and returns the model. // // Notes: The model is returned from the method if asynchronous is false. // Otherwise, returns null and passes the completed model via the // block instead. // // ============================================================================== // - (CGImageRef) readImageAtPath(String imagePath // boolean asynchronous // completionHandler:(void (^)(CGImageRef))completionBlock // { // DispatchGroup group = null; // #if USE_BLOCKS // __block // #endif // CGImageRef image = null; // // #if USE_BLOCKS // group = dispatch_group_create(); // #endif // // #if USE_BLOCKS // if(asynchronous == false) // { // dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // #endif // image =[LDrawUtilities.imageAtPath:imagePath(); // #if USE_BLOCKS // } // else // { // dispatch_group_notify(group, // dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), // ^{ // image =[LDrawUtilities.imageAtPath:imagePath(); // // if(completionBlock) // completionBlock(image); // }); // } // // dispatch_release(group); // #endif // // return (CGImageRef)[(id)image autorelease(); // // }//end readImageAtPath: // ========== readModelAtPath:asynchronously:completionHandler: // ================= // // Purpose: Parses the model found at the given path, adds it to the list of // loaded parts, and returns the model. // // Notes: The model is returned from the method if asynchronous is false. // Otherwise, returns null and passes the completed model via the // block instead. // // ============================================================================== public LDrawModel readModelAtPath(String partPath, DispatchGroup parentGroup, LDrawModel completionBlock) { return readModelAtPath(partPath, parentGroup, completionBlock, true); } public LDrawModel readModelAtPath(String partPath, DispatchGroup parentGroup, LDrawModel completionBlock, boolean optimize) { LDrawFile parsedFile = null; LDrawModel model = null; if (partPath != null) { // We found it in the LDraw folder; now all we need to do is get the // model for it. long t = System.nanoTime(); parsedFile = LDrawFile.fileFromContentsAtPath(partPath); // System.out.println(partPath + ": " + (System.nanoTime() - t)); } if (parsedFile == null) return null; model = parsedFile.submodels().get(0); if (optimize) { long flattenWeight = model.getFlattenWeight(1); // long flattenDepth = model.getFlattenDepth(1); // System.out.println(allEnclosedElements); if (flattenWeight < 100000) model.optimizeStructure(); // else // System.out.println(partPath + ": " + flattenWeight); } if(parentGroup.isCCW()==false){ for(LDrawDirective step : model.subdirectives()){ for(LDrawDirective directive : ((LDrawStep)step).subdirectives()){ if(directive instanceof LDrawTriangle){ ((LDrawTriangle)directive).setToCW(); }else if(directive instanceof LDrawQuadrilateral){ ((LDrawQuadrilateral)directive).setToCW(); } } } } return model; }// end readModelAtPath: }